#!/bin/groovy
/*
 * Licensed to the OpenAirInterface (OAI) Software Alliance under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The OpenAirInterface Software Alliance licenses this file to You under
 * the OAI Public License, Version 1.1  (the "License"); you may not use this file
 * except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.openairinterface.org/?page_id=698
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *-------------------------------------------------------------------------------
 * For more information about the OpenAirInterface (OAI) Software Alliance:
 *      contact@openairinterface.org
 */

//-------------------------------------------------------------------------------

// Location of the CN executor node
// Its main purpose is the Ubuntu Build
ubuntuNode = params.UbuntuBuildNode
ubuntuBuildResource = params.UbuntuBuildResource

// Location of the RHEL CN executor
rhelNode = params.RhelBuildNode
rhelResource = params.RhelBuildResource
rhelOcCredentials = params.RhelOcCredentials

// Location of the CPPCHECK executor
cppcheckNode = params.CppCheckNode
cppcheckResource = params.CppCheckResource

// Location of the CLANG-FORMAT-CHECK executor
formatCheckNode = params.FormatCheckNode
formatCheckResource = params.FormatCheckResource

// Tags/Branches to use
def amf_tag = "develop"
def amf_branch = "develop"

// Merge Request Link
gitlabMergeRequestLink = ''
gitCommittorEmailAddr  = ''

// Docker Hub account to push to
DH_Account = "oaisoftwarealliance"

// Private Local Registry URL
PrivateRegistryURL = 'selfix.sboai.cs.eurecom.fr'

//-------------------------------------------------------------------------------
// Pipeline start
pipeline {
  agent {
    label ubuntuNode
  }
  options {
    disableConcurrentBuilds()
    timestamps()
    ansiColor('xterm')
    gitLabConnection('OAI GitLab')
    // Minimal checks
    gitlabBuilds(builds: [
      "Build Ubuntu AMF Image",
      "Build Ubuntu AMF Image with LTTNG",
      "Build RHEL AMF Image",
      "Static Code Analysis",
      "Code Formatting Checker"
    ])
  }

  stages {
    stage ('Verify Parameters') {
      steps {
        script {
          echo '\u2705 \u001B[32mVerify Parameters\u001B[0m'

          JOB_TIMESTAMP = sh returnStdout: true, script: 'date --utc --rfc-3339=seconds | sed -e "s#+00:00##"'
          JOB_TIMESTAMP = JOB_TIMESTAMP.trim()

          if (params.RhelOcCredentials == null) {
            echo '\u26D4 \u001B[31mNo Credentials to connect to Openshift!\u001B[0m'
            error "Stopping pipeline!"
          }
          if (params.DockerHubCredentials == null) {
            echo '\u26D4 \u001B[31mNo Credentials to push to DockerHub!\u001B[0m'
            error "Stopping pipeline!"
          }
        }
      }
    }
    stage ('Prepare Source Code') {
      steps {
        script {
          if ("MERGE".equals(env.gitlabActionType)) {
            gitlabMergeRequestLink = sh returnStdout: true, script: "curl --silent 'https://gitlab.eurecom.fr/api/v4/projects/oai%2Fcn5g%2Foai-cn5g-amf/merge_requests/${env.gitlabMergeRequestIid}' | jq .web_url | sed 's#\"##g'"
            gitlabMergeRequestLink = gitlabMergeRequestLink.trim()
            gitCommittorEmailAddr  = env.gitlabUserEmail

            shortenShaOne = sh returnStdout: true, script: 'git log -1 --pretty=format:"%h" --abbrev=8 ' + env.gitlabMergeRequestLastCommit
            shortenShaOne = shortenShaOne.trim()
            amf_tag       = 'ci-tmp-pr-' + env.gitlabMergeRequestIid + '-' + shortenShaOne
            amf_branch    = env.gitlabSourceBranch

            echo "========= THIS IS A MERGE REQUEST =========="
            echo "MR ID       is ${env.gitlabMergeRequestIid}"
            echo "MR LINK     is ${gitlabMergeRequestLink}"
            echo "MR TITLE    is ${env.gitlabMergeRequestTitle}"
            echo "MR Usermail is ${gitCommittorEmailAddr}"
            echo "MR TAG      is ${amf_tag}"
          } else {
            gitCommittorEmailAddr = sh returnStdout: true, script: 'git log -n1 --pretty=format:%ae ${GIT_COMMIT}'
            gitCommittorEmailAddr = gitCommittorEmailAddr.trim()

            shortenShaOne = sh returnStdout: true, script: 'git log -1 --pretty=format:"%h" --abbrev=8 ' + env.GIT_COMMIT
            shortenShaOne = shortenShaOne.trim()
            amf_tag       = 'develop-' + shortenShaOne
            amf_branch    = env.GIT_COMMIT

            echo "======== THIS IS A PUSH REQUEST ========"
            echo "Git Branch      is ${GIT_BRANCH}"
            echo "Git Commit      is ${GIT_COMMIT}"
            echo "CI  Usermail    is ${gitCommittorEmailAddr}"
            echo "CI develop TAG  is ${amf_tag}"
          }
          prepareWorkspaceMergeCase()
        }
      }
      post {
        failure {
          script {
            def message = "OAI " + JOB_NAME + " build (" + BUILD_ID + "): Merge Conflicts -- Cannot perform CI"
            addGitLabMRComment comment: message
            currentBuild.result = 'FAILURE'
          }
        }
      }
    }
    stage('Build Core Network Function') {
      parallel {
        stage ('Build Ubuntu AMF Image') {
          steps {
            // Now it is only locked during this build stage and not for the whole pipeline
            lock(ubuntuBuildResource) {
              script {
                gitlabCommitStatus(name: "Build Ubuntu AMF Image") {
                  sh "docker image rm oai-amf:${amf_tag} || true"
                  sh "docker image prune --force"
                  if ("PUSH".equals(env.gitlabActionType)) {
                    dockerBuildOptions = '--no-cache '
                  }
                  if ("MERGE".equals(env.gitlabActionType)) {
                    dockerBuildOptions = ''
                  }
                  sh "docker buildx build ${dockerBuildOptions} --target oai-amf --tag oai-amf:${amf_tag} --file docker/Dockerfile.amf.ubuntu . > archives/amf_ubuntu_image_build.log 2>&1"
                  // Putting a place holder to try out on the flattening of image.
                  // If not satisfactory, we can remove it.
                  sh "python3 ./ci-scripts/flatten_image.py --tag oai-amf:${amf_tag}"
                  sh "docker image prune --force"
                  sh "docker image ls | egrep --color=never 'amf|REPOSITORY' >> archives/amf_ubuntu_image_build.log"
                  // Pushing to local private registry for testing purpose
                  sh "docker login -u oaicicd -p oaicicd ${PrivateRegistryURL}"
                  sh "docker image tag oai-amf:${amf_tag} ${PrivateRegistryURL}/oai-amf:${amf_tag}"
                  sh "docker push ${PrivateRegistryURL}/oai-amf:${amf_tag}"
                  // Remove all images locally
                  sh "docker rmi oai-amf:${amf_tag} ${PrivateRegistryURL}/oai-amf:${amf_tag}"
                  sh "docker logout ${PrivateRegistryURL}"
                }
              }
            }
          }
          post {
            success {
              sh "echo 'OAI-AMF UBUNTU IMAGE BUILD: OK' >> archives/amf_ubuntu_image_build.log"
            }
            unsuccessful {
              sh "echo 'OAI-AMF UBUNTU IMAGE BUILD: KO' >> archives/amf_ubuntu_image_build.log"
            }
          }
        }
        stage ('Build Ubuntu AMF Image with LTTNG') {
          steps {
            // I want the previous stage to run always before to profit from the cache
            // This stage only exists to make sure we don't have a regression in LTTNG build
            // Generated image is not used
            sleep 30
            lock(ubuntuBuildResource) {
              script {
                gitlabCommitStatus(name: "Build Ubuntu AMF Image with LTTNG") {
                  sh "docker image rm oai-amf:lttng-build || true"
                  sh "docker image prune --force"
                  sh "docker buildx build --target oai-amf --tag oai-amf:lttng-build --file docker/Dockerfile.amf.ubuntu --build-arg ENABLE_LTTNG=true . > archives/amf_ubuntu_lttng_image_build.log 2>&1"
                  sh "docker image prune --force"
                  sh "docker image rm oai-amf:lttng-build || true"
                }
              }
            }
          }
          post {
            success {
              sh "echo 'OAI-AMF UBUNTU IMAGE BUILD: OK' >> archives/amf_ubuntu_lttng_image_build.log"
            }
            unsuccessful {
              sh "echo 'OAI-AMF UBUNTU IMAGE BUILD: KO' >> archives/amf_ubuntu_lttng_image_build.log"
            }
          }
        }
        stage ('Build RHEL AMF Image') {
          agent { label rhelNode }
          steps {
            lock (rhelResource) {
              script {
                gitlabCommitStatus(name: "Build RHEL AMF Image") {
                  // It's a different agent from main one.
                  prepareWorkspaceMergeCase()
                  withCredentials([
                    [$class: 'UsernamePasswordMultiBinding', credentialsId: "${rhelOcCredentials}", usernameVariable: 'OC_Username', passwordVariable: 'OC_Password']
                  ]) {
                    sh "oc login -u ${OC_Username} -p ${OC_Password}"
                  }
                  sh "oc delete istag oai-amf:${amf_tag} || true"
                  // Copy the RHEL Host certificates for building
                  sh "./ci-scripts/common/python/recreate_entitlement.py"
                  // Building
                  sh "oc delete -f openshift/build-config.yaml || true"
                  sh "sed -i -e 's@oai-amf:latest@oai-amf:${amf_tag}@g' openshift/build-config.yaml"
                  sh "oc create -f openshift/build-config.yaml"
                  sh 'oc start-build amf-build-cfg  --from-dir=./ --exclude=""'
                  // need python to wait for pod amf-build-cfg-1-build to be Completed or Error
                  // it fails if it detects error or timeout at 20 minutes
                  sh "./ci-scripts/common/python/check_build_pod_status.py --pod-name amf-build-cfg-1-build --log-file archives/amf_rhel_image_build.log"
                  sh "oc describe istag oai-amf:${amf_tag} | grep 'Image Size:' >> archives/amf_rhel_image_build.log"
                }
              }
            }
          }
          post {
            success {
              sh "echo 'OAI-AMF RHEL IMAGE BUILD: OK' >> archives/amf_rhel_image_build.log"
            }
            unsuccessful {
              sh "echo 'OAI-AMF RHEL IMAGE BUILD: KO' >> archives/amf_rhel_image_build.log"
            }
            cleanup {
              script {
                sh "oc delete build amf-build-cfg-1 || true"
                sh "oc logout || true"
                stash allowEmpty: true, includes: 'archives/amf_rhel_image_build.log', name: 'rhelBuildLog'
              }
            }
          }
        }
        // Running CPPCHECK in parallel to gain time
        stage ('Static Code Analysis') {
          agent { label cppcheckNode }
          steps {
            lock (cppcheckResource) {
              script {
                gitlabCommitStatus(name: "Static Code Analysis") {
                  // It's a different agent from main one.
                  prepareWorkspaceMergeCase()
                  // Moving to focal and cppcheck 1.90 and a dockerfile approach
                  sh 'sed -i -e "s@nfName@amf@" ci-scripts/common/docker/Dockerfile.ci.cppcheck'
                  sh 'docker build --target amf-cppcheck --tag amf-cppcheck:test --file ci-scripts/common/docker/Dockerfile.ci.cppcheck . > archives/cppcheck_install.log 2>&1'
                  sh 'docker run --name amf-ci-cppcheck --entrypoint /bin/true amf-cppcheck:test'
                  sh 'docker cp amf-ci-cppcheck:/home/cppcheck.xml archives'
                  sh 'docker cp amf-ci-cppcheck:/home/cppcheck_build.log archives'
                  sh 'docker rm -f amf-ci-cppcheck'
                  sh 'docker rmi amf-cppcheck:test'
                }
              }
            }
          }
          post {
            success {
              sh "echo 'CPPCHECK: OK' >> archives/cppcheck_install.log"
            }
            unsuccessful {
              sh "echo 'CPPCHECK: KO' >> archives/cppcheck_install.log"
            }
            cleanup {
              script {
                stash allowEmpty: true, includes: 'archives/cppcheck*.*', name: 'cppcheckLogs'
                // no need to keep the cppcheck container
                sh 'docker rm -f amf-ci-cppcheck || true'
                sh 'docker rmi amf-cppcheck:test || true'
              }
            }
          }
        }
        // Running CLANG-FORMATTING check in parallel to gain time
        stage ('Code Formatting Checker') {
          agent { label formatCheckNode }
          steps {
            lock (formatCheckResource) {
              script {
                gitlabCommitStatus(name: "Code Formatting Checker") {
                  // It's a different agent from main one.
                  prepareWorkspaceMergeCase()
                  sh 'sed -i -e "s@nfName@amf@" ci-scripts/common/docker/Dockerfile.ci.clang-format'
                  if ("MERGE".equals(env.gitlabActionType)) {
                    sh 'docker build --target amf-clang-format-check --tag amf-clang-format-check:test --file ci-scripts/common/docker/Dockerfile.ci.clang-format --build-arg MERGE_REQUEST_CHECK=True --build-arg SOURCE_BRANCH=' + env.gitlabSourceBranch + ' --build-arg TARGET_BRANCH=' + env.gitlabTargetBranch + ' . > archives/clang_format_install.log 2>&1'
                  } else {
                    sh 'docker build --target amf-clang-format-check --tag amf-clang-format-check:test --file ci-scripts/common/docker/Dockerfile.ci.clang-format . > archives/clang_format_install.log 2>&1'
                  }
                  sh 'docker run --name amf-ci-clang-format --entrypoint /bin/true amf-clang-format-check:test'
                  sh 'docker cp amf-ci-clang-format:/home/src/oai_rules_result.txt src'
                  sh 'docker cp amf-ci-clang-format:/home/src/oai_rules_result_list.txt src || true'
                  sh 'docker rm -f amf-ci-clang-format'
                  sh 'docker rmi amf-clang-format-check:test'
                  // The check is done now here
                  sh 'grep -L "NB_FILES_FAILING_CHECK=0" src/oai_rules_result.txt'
                }
              }
            }
          }
          post {
            cleanup {
              script {
                stash allowEmpty: true, includes: 'src/oai_rules_result*.txt, archives/clang_format_install.log', name: 'formatCheckLogs'
                sh 'docker rm -f amf-ci-clang-format || true'
                sh 'docker rmi amf-clang-format-check:test || true'
              }
            }
          }
        }
      }
      post {
        always {
          script {
            unstash 'rhelBuildLog'
            unstash 'cppcheckLogs'
            unstash 'formatCheckLogs'
          }
        }
      }
    }
    stage('Testing whole 5g Core Network Functions') {
      parallel {
        stage ('Testing the tutorials') {
          steps {
            script {
              gitlabCommitStatus(name: "Test tutorials") {
                localStatus = build job: 'OAI-CN5G-Tutorials-Check',
                  parameters: [
                    string(name: 'AMF_TAG', value: String.valueOf(amf_tag)),
                    string(name: 'AMF_BRANCH', value: String.valueOf(amf_branch))
                  ], propagate: false
                localResult = localStatus.getResult()

                if (localStatus.resultIsBetterOrEqualTo('SUCCESS')) {
                  echo "Tutorials Test Job is OK"
                } else {
                  error "Tutorials Test Job is KO"
                }
              }
            }
          }
          post {
            always {
              script {
                copyArtifacts(projectName: 'OAI-CN5G-Tutorials-Check',
                              filter: '*_results_oai_cn5g*.html',
                              selector: lastCompleted())
              }
            }
          }
        }
        stage ('Testing with COTS-UE') {
          steps {
            script {
              gitlabCommitStatus(name: "Test with COTS-UE") {
                localStatus = build job: 'OAI-CN5G-COTS-UE-Test',
                  parameters: [
                    string(name: 'AMF_TAG', value: String.valueOf(amf_tag)),
                    string(name: 'AMF_BRANCH', value: String.valueOf(amf_branch))
                  ], propagate: false
                localResult = localStatus.getResult()

                if (localStatus.resultIsBetterOrEqualTo('SUCCESS')) {
                  echo "Test Job with COTS-UE is OK"
                } else {
                  error "Test Job with COTS-UE is is KO"
                }
              }
            }
          }
          post {
            always {
              script {
                copyArtifacts(projectName: 'OAI-CN5G-COTS-UE-Test',
                              filter: '*_results_oai_cn5g*.html',
                              selector: lastCompleted())
              }
            }
          }
        }
        stage ('Load Testing') {
          steps {
            script {
              gitlabCommitStatus(name: "Load Testing") {
                localStatus = build job: 'OAI-CN5G-Load-Test',
                  parameters: [
                    string(name: 'AMF_TAG', value: String.valueOf(amf_tag)),
                    string(name: 'AMF_BRANCH', value: String.valueOf(amf_branch))
                  ], propagate: false
                localResult = localStatus.getResult()

                if (localStatus.resultIsBetterOrEqualTo('SUCCESS')) {
                  echo "Load Testing is OK"
                } else {
                  error "Load Testing is is KO"
                }
              }
            }
          }
          post {
            always {
              script {
                copyArtifacts(projectName: 'OAI-CN5G-Load-Test',
                              filter: '*_results_oai_cn5g*.html',
                              selector: lastCompleted())
              }
            }
          }
        }
        // Home-made RAN emulator
        stage ('Robot-Test') {
          steps {
            script {
              gitlabCommitStatus(name: "Robot-Test") {
                localStatus = build job: 'OAI-CN5G-RobotTest',
                  parameters: [
                    string(name: 'AMF_TAG', value: String.valueOf(amf_tag)),
                    string(name: 'AMF_BRANCH', value: String.valueOf(amf_branch))
                  ], propagate: false
                localResult = localStatus.getResult()

                if (localStatus.resultIsBetterOrEqualTo('SUCCESS')) {
                  echo "Robot-Test is OK"
                } else {
                  error "Robot-Test is is KO"
                }
              }
            }
          }
          post {
            always {
              script {
                copyArtifacts(projectName: 'OAI-CN5G-RobotTest',
                              filter: '*.html',
                              selector: lastCompleted())
              }
            }
          }
        }
      }
    }
    // We are only publishing the Ubuntu image to Docker-Hub
    // For Post-Merge events.
    // Temporary Images from Merge-Request Runs are kept in local private registry
    stage ('Pushing Image to Official Registry') {
      steps {
        lock(ubuntuBuildResource) {
          script {
            // Only in case of push to target branch!
            if ("PUSH".equals(env.gitlabActionType)) {
              withCredentials([
                [$class: 'UsernamePasswordMultiBinding', credentialsId: "${params.DockerHubCredentials}", usernameVariable: 'DH_Username', passwordVariable: 'DH_Password']
              ]) {
                sh "echo ${DH_Password} | docker login --username ${DH_Username} --password-stdin"
              }
              sh "docker login -u oaicicd -p oaicicd ${PrivateRegistryURL}"
              sh "docker pull ${PrivateRegistryURL}/oai-amf:${amf_tag}"
              sh "docker image tag ${PrivateRegistryURL}/oai-amf:${amf_tag} ${DH_Account}/oai-amf:develop"
              sh "docker push ${DH_Account}/oai-amf:develop"
              sh "docker rmi ${DH_Account}/oai-amf:develop ${PrivateRegistryURL}/oai-amf:${amf_tag}"
              sh "docker logout ${PrivateRegistryURL}"
              sh "docker logout"
            }
          }
        }
      }
    }
  }
  post {
    success {
      script {
        if ("MERGE".equals(env.gitlabActionType)) {
          def message = "OAI " + JOB_NAME + " build (" + BUILD_ID + "): passed (" + BUILD_URL + ")"
          echo "This is a MERGE event"
          addGitLabMRComment comment: message
        }
      }
    }
    unsuccessful {
      script {
        if ("MERGE".equals(env.gitlabActionType)) {
          def message = "OAI " + JOB_NAME + " build (" + BUILD_ID + "): failed (" + BUILD_URL + ")"
          echo "This is a MERGE event"
          addGitLabMRComment comment: message
        }
      }
    }
    cleanup {
      script {
        // Zipping all archived log files
        sh "zip -r -qq docker_logs.zip archives"
        if (fileExists('docker_logs.zip')) {
          archiveArtifacts artifacts: 'docker_logs.zip'
        }

        // Generating the HTML report(s)
        if ("MERGE".equals(env.gitlabActionType)) {
          sh "./ci-scripts/generateHtmlReport.py --job-name ${JOB_NAME} --build-id ${BUILD_ID} --build-url ${BUILD_URL} --git-url ${GIT_URL} --git-src-branch ${env.gitlabSourceBranch} --git-src-commit ${env.gitlabMergeRequestLastCommit} --git-merge-request --git-dst-branch ${env.gitlabTargetBranch} --git-dst-commit ${GIT_COMMIT}"
        } else {
          sh "./ci-scripts/generateHtmlReport.py --job-name ${JOB_NAME} --build-id ${BUILD_ID} --build-url ${BUILD_URL} --git-url ${GIT_URL} --git-src-branch ${GIT_BRANCH} --git-src-commit ${GIT_COMMIT}"
        }
        listOfFiles = sh returnStdout: true, script: 'ls test_results*.html'
        String[] htmlFiles = listOfFiles.split("\\n")
        for (htmlFile in htmlFiles) {
          if ("MERGE".equals(env.gitlabActionType)) {
            sh "sed -i -e 's#TEMPLATE_MERGE_REQUEST_LINK#${gitlabMergeRequestLink}#g' ${htmlFile}"
            sh "sed -i -e 's#TEMPLATE_MERGE_REQUEST_TEMPLATE#${env.gitlabMergeRequestTitle}#' ${htmlFile}"
          }
          sh "sed -i -e 's#TEMPLATE_TIME#${JOB_TIMESTAMP}#' ${htmlFile}"
          archiveArtifacts artifacts: htmlFile
        }

        // Sending email to commiter
        if (params.sendToCommitterEmail != null) {
          if (params.sendToCommitterEmail) {
            emailext attachmentsPattern: '*results*.html',
              body: '''Hi,


Here are attached HTML report files for $PROJECT_NAME - Build # $BUILD_NUMBER - $BUILD_STATUS!

Regards,
OAI CI Team''',
              replyTo: 'no-reply@openairinterface.org',
              subject: '$PROJECT_NAME - Build # $BUILD_NUMBER - $BUILD_STATUS!',
              to: gitCommittorEmailAddr
          }
        }
      }
    }
  }
}

def prepareWorkspaceMergeCase () {
  sh "git clean -x -d -f > /dev/null 2>&1"
  sh "git submodule foreach --recursive 'git clean -x -d -ff' > /dev/null 2>&1"
  sh "git submodule deinit --force --all > /dev/null 2>&1"
  if ("MERGE".equals(env.gitlabActionType)) {
    sh "./ci-scripts/doGitLabMerge.sh --src-branch ${env.gitlabSourceBranch} --src-commit ${env.gitlabMergeRequestLastCommit} --target-branch ${env.gitlabTargetBranch} --target-commit ${GIT_COMMIT}"
  }
  sh "git submodule update --init --recursive"
  sh "mkdir -p archives"
}
